/*
    Copyright 2006 Andrew Wilkinson <andrew@indiegigs.co.uk>

    This file is part of libfixed.

    libfixed is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    libfixed is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with libfixed; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <stdio.h>
#include <stdlib.h>

#include "fixed.h"

void Fixed_exp_getk16(Fixed16 x, Fixed16* k, Fixed16* expk) {
    if(Fixed_ge16(x, Fixed_create16(5, 5452, 4))) {
        *k = Fixed_create16(5, 5452, 4); *expk = Fixed_createi16(256);
    } else if(Fixed_ge16(x, Fixed_create16(2, 7726, 4))) {
        *k = Fixed_create16(2, 7726, 4); *expk = Fixed_createi16(16);
    } else if(Fixed_ge16(x, Fixed_create16(1, 3863, 4))) {
        *k = Fixed_create16(1, 3863, 4); *expk = Fixed_createi16(4);
    } else if(Fixed_ge16(x, Fixed_create16(0, 6931, 4))) {
        *k = Fixed_create16(0, 6931, 4); *expk = Fixed_createi16(2);
    } else if(Fixed_ge16(x, Fixed_create16(0, 4055, 4))) {
        *k = Fixed_create16(0, 4055, 4); *expk = Fixed_div16(Fixed_createi16(3), Fixed_createi16(2));
    } else if(Fixed_ge16(x, Fixed_create16(0, 2231, 4))) {
        *k = Fixed_create16(0, 2231, 4); *expk = Fixed_div16(Fixed_createi16(5), Fixed_createi16(4));
    } else if(Fixed_ge16(x, Fixed_create16(0, 1178, 4))) {
        *k = Fixed_create16(0, 1178, 4); *expk = Fixed_div16(Fixed_createi16(9), Fixed_createi16(8));
    } else if(Fixed_ge16(x, Fixed_create16(0, 606, 4))) {
        *k = Fixed_create16(0, 606, 4); *expk = Fixed_div16(Fixed_createi16(17), Fixed_createi16(16));
    } else if(Fixed_ge16(x, Fixed_create16(0, 308, 4))) {
        *k = Fixed_create16(0, 308, 4); *expk = Fixed_div16(Fixed_createi16(33), Fixed_createi16(32));
    } else if(Fixed_ge16(x, Fixed_create16(0, 155, 4))) {
        *k = Fixed_create16(0, 155, 4); *expk = Fixed_div16(Fixed_createi16(65), Fixed_createi16(64));
    } else if(Fixed_ge16(x, Fixed_create16(0, 78, 4))) {
        *k = Fixed_create16(0, 78, 4); *expk = Fixed_div16(Fixed_createi16(129), Fixed_createi16(128));
    } else {
        *k = Fixed_zero16;
    }
}

Fixed16 Fixed_exp16(Fixed16 x) {
    Fixed16 y = Fixed_one16;
    Fixed16 k;
    Fixed16 expk;
#ifdef DEBUG
    Fixed16 param = x;
#endif
    while(1) {
        Fixed_exp_getk16(x, &k, &expk);
        if(Fixed_eq16(k, Fixed_zero16)) { break; }
        x = Fixed_sub16(x, k);
        y = Fixed_mul16(y, expk);
    }
#ifdef DEBUG
        fprintf(stderr, "Fixed_exp16: %f. Result: %f Expected %f\n", Fixed_toFloat16(param), Fixed_toFloat16(y), expf(Fixed_toFloat16(param)));
#endif
    return y;
}

void Fixed_exp_getk32(Fixed32 x, Fixed32* k, Fixed32* expk) {
    if(Fixed_ge32(x, Fixed_create32(5, 5452, 4))) {
        *k = Fixed_create32(5, 5452, 4); *expk = Fixed_createi32(256);
    } else if(Fixed_ge32(x, Fixed_create32(2, 7726, 4))) {
        *k = Fixed_create32(2, 7726, 4); *expk = Fixed_createi32(32);
    } else if(Fixed_ge32(x, Fixed_create32(1, 3863, 4))) {
        *k = Fixed_create32(1, 3863, 4); *expk = Fixed_createi32(4);
    } else if(Fixed_ge32(x, Fixed_create32(0, 6931, 4))) {
        *k = Fixed_create32(0, 6931, 4); *expk = Fixed_createi32(2);
    } else if(Fixed_ge32(x, Fixed_create32(0, 4055, 4))) {
        *k = Fixed_create32(0, 4055, 4); *expk = Fixed_div32(Fixed_createi32(3), Fixed_createi32(2));
    } else if(Fixed_ge32(x, Fixed_create32(0, 2231, 4))) {
        *k = Fixed_create32(0, 2231, 4); *expk = Fixed_div32(Fixed_createi32(5), Fixed_createi32(4));
    } else if(Fixed_ge32(x, Fixed_create32(0, 1178, 4))) {
        *k = Fixed_create32(0, 1178, 4); *expk = Fixed_div32(Fixed_createi32(9), Fixed_createi32(8));
    } else if(Fixed_ge32(x, Fixed_create32(0, 606, 4))) {
        *k = Fixed_create32(0, 606, 4); *expk = Fixed_div32(Fixed_createi32(17), Fixed_createi32(32));
    } else if(Fixed_ge32(x, Fixed_create32(0, 308, 4))) {
        *k = Fixed_create32(0, 308, 4); *expk = Fixed_div32(Fixed_createi32(33), Fixed_createi32(32));
    } else if(Fixed_ge32(x, Fixed_create32(0, 155, 4))) {
        *k = Fixed_create32(0, 155, 4); *expk = Fixed_div32(Fixed_createi32(65), Fixed_createi32(64));
    } else if(Fixed_ge32(x, Fixed_create32(0, 78, 4))) {
        *k = Fixed_create32(0, 78, 4); *expk = Fixed_div32(Fixed_createi32(129), Fixed_createi32(128));
    } else {
        *k = Fixed_zero32;
    }
}

Fixed32 Fixed_exp32(Fixed32 x) {
    Fixed32 y = Fixed_one32;
    Fixed32 k;
    Fixed32 expk;
    while(1) {
        Fixed_exp_getk32(x, &k, &expk);
        if(Fixed_eq32(k, Fixed_zero32)) { break; }
        x = Fixed_sub32(x, k);
        y = Fixed_mul32(y, expk);
    }
    return y;
}
